#include <stdint.h>
#include <math.h>
#include <stdio.h>

#include "py/obj.h"
#include "py/gc.h"
#include "py/runtime.h"
#include "mphalport.h"

#include "mod/machine.h"

STATIC mp_obj_t machine_pwm_deinit(mp_obj_t self_in, mp_obj_t ch_o);

static const pwm_stat_desc_t _stat = {
    .cnt = {5, 1, 3, 1, 2},
    .ch = {
        (const pwm_stat_bit_desc_t[5]){{0,"PWM0"},{5,"SYNC2"},{7,"SYNC5"},{8,"COMP2"},{10,"CAP=true"}},
        (const pwm_stat_bit_desc_t[1]){{1,"PWM1"}},
        (const pwm_stat_bit_desc_t[3]){{2,"PWM2"},{6,"SYNC2"},{9,"COMP2"}},
        (const pwm_stat_bit_desc_t[1]){{3,"PWM3"}},
        (const pwm_stat_bit_desc_t[2]){{4,"PWM4"},{11,"CAP=true"}},
    },
};


// reset pwm
static void _pwm_deinit(PWM_HandleTypeDef *hpwm)
{
    HAL_PWM_DeInit(hpwm);
}


// start capture(DMA mode)
static void _pwm_cap_start(PWM_HandleTypeDef* hpwm, pwm_cap_desc_t * cap_desc)
{
    __HAL_RCC_DMA_CLK_ENABLE();
    
    cap_desc->hdma.Instance = (hpwm->Channel == 0) ? (MICROPY_HW_CAP0_DMA) : (MICROPY_HW_CAP1_DMA);  // 只支持PWM0(DMA通道6), PWM1(DMA通道7)
    cap_desc->hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;
    cap_desc->hdma.Init.DestInc = DMA_DINC_CIRCULAR;
    cap_desc->hdma.Init.SrcInc = DMA_SINC_DISABLE;
    cap_desc->hdma.Init.DataAlignment = DMA_DATAALIGN_WORD;
    cap_desc->hdma.Init.Mode = DMA_MODE_NORMAL_CIRCULAR;
    cap_desc->hdma.Init.RequestSourceSel = (hpwm->Channel == 0) ? (DMA_REQUEST_SOURCE_PWM_CAP0) : (DMA_REQUEST_SOURCE_PWM_CAP1); // DMA请求源设置
    HAL_DMA_Init(&cap_desc->hdma);
    
    // enable DMA IRQ
    //NVIC_SetPriority(DMA_Channel4_7_IRQn, 3);
    //NVIC_EnableIRQ(DMA_Channel4_7_IRQn);
    
    // start DMA transfer, start pwm counter
    uint32_t src = (uint32_t)((hpwm->Channel == 0) ? (&hpwm->Instance->CH0CAPDAT) : (&hpwm->Instance->CH4CAPDAT));
    memset(cap_desc->capbuf, 0, sizeof(cap_desc->capbuf));  // clear buffer
    HAL_DMA_Start(&cap_desc->hdma, src, (uint32_t)cap_desc->capbuf, sizeof(cap_desc->capbuf));
    HAL_PWM_Start(hpwm, hpwm->Channel);
}

// stop capture(DMA mode)
static void _pwm_cap_stop(PWM_HandleTypeDef* hpwm, pwm_cap_desc_t *cap_desc)
{
     HAL_PWM_Stop(hpwm, hpwm->Channel);  // stop PWM counter
    __HAL_DMA_DISABLE(&cap_desc->hdma);   // stop dma transfer
}

//////////////////////////////////////////////////////////////////////////////////////////////

// get frequency
static void _pwm_get_freq_period(PWM_HandleTypeDef *hpwm, uint32_t *psc, uint32_t *period)
{
    uint32_t spsc = 0;  // s for shadowm
    uint32_t sprd = 0;
    
    if (hpwm->Channel == PWM_CHANNEL_0){
        spsc = READ_BIT(hpwm->Instance->CLKDIV01, PWM_CLKDIV01_CH0) >> PWM_CLKDIV01_CH0_Pos;
        sprd = READ_BIT(hpwm->Instance->PERIOD, PWM_PERIOD_CH0) >> PWM_PERIOD_CH0_Pos;
    }else if (hpwm->Channel == PWM_CHANNEL_1){
        spsc = READ_BIT(hpwm->Instance->CLKDIV01, PWM_CLKDIV01_CH1) >> PWM_CLKDIV01_CH1_Pos;
        sprd = READ_BIT(hpwm->Instance->PERIOD, PWM_PERIOD_CH1) >> PWM_PERIOD_CH1_Pos;
    }else if (hpwm->Channel == PWM_CHANNEL_2){
        spsc = READ_BIT(hpwm->Instance->CLKDIV23, PWM_CLKDIV23_CH2) >> PWM_CLKDIV23_CH2_Pos;
        sprd = READ_BIT(hpwm->Instance->PERIOD, PWM_PERIOD_CH2) >> PWM_PERIOD_CH2_Pos;
    }else if (hpwm->Channel == PWM_CHANNEL_3){
        spsc = READ_BIT(hpwm->Instance->CLKDIV23, PWM_CLKDIV23_CH3) >> PWM_CLKDIV23_CH3_Pos;
        sprd = READ_BIT(hpwm->Instance->PERIOD, PWM_PERIOD_CH3) >> PWM_PERIOD_CH3_Pos;
    }else if (hpwm->Channel == PWM_CHANNEL_4){
        spsc = READ_BIT(hpwm->Instance->CH4CR1, PWM_CH4CR1_DIV) >> PWM_CH4CR1_DIV_Pos;
        sprd =  READ_BIT(hpwm->Instance->CH4CR1, PWM_CH4CR1_PRD) >> PWM_CH4CR1_PRD_Pos;
    }
    if(psc) *psc = spsc;
    if(period) *period = sprd;
}

// 获取自动重载位(ARR)
bool _pwm_get_reload(PWM_HandleTypeDef *hpwm)
{
    if (hpwm->Channel == PWM_CHANNEL_4){
        return READ_BIT(hpwm->Instance->CH4CR2, PWM_CH4CR2_CNTMODE);
    }else{
        return READ_BIT(hpwm->Instance->CR, (0x01 << (PWM_CR_CNTMODE_Pos + hpwm->Channel)));
    }
}

// 获取PWM输出翻转位或输入捕获翻转位
bool _pwm_get_inv(PWM_HandleTypeDef *hpwm, bool is_cap_inv)
{
    if(is_cap_inv){
        if (hpwm->Channel == PWM_CHANNEL_4){
            return READ_BIT(hpwm->Instance->CH4CR2, PWM_CH4CR2_PINV);
        }else{
            return READ_BIT(hpwm->Instance->CR, PWM_CR_CAPINV);
        }
    }else{
        if (hpwm->Channel == PWM_CHANNEL_4){
            return READ_BIT(hpwm->Instance->CH4CR2, PWM_CH4CR2_PINV);
        }else{
            return READ_BIT(hpwm->Instance->CR, (0x01 << (PWM_CR_PINV_Pos + hpwm->Channel)));
        }
    }
}


STATIC void machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
    uint32_t psc, period;
    machine_pwm_obj_t *self = MP_OBJ_FROM_PTR(self_in);
    
    mp_printf(print, "PWM([");
    for(int i = 0; i < MICROPY_HW_MAX_PWM_CNT; i++){
        // PWM名称, "PWMx("
        if(self->stat & (1 << _stat.ch[i][0].bit)){
            mp_printf(print, "%s%s(",((i) ? ("     ") : ("")), _stat.ch[i][0].info);  // 输出"\tPWMx("
            // 显示psc, reload
            _pwm_get_freq_period(&self->hpwm[i], &psc, &period);
            mp_printf(print, "psc=%d, reload=%s", psc, (_pwm_get_reload(&self->hpwm[i]) ? ("true") : ("false")));

            // 根据PWMx,检查对应的状态位(显示同步或互补模式, sync5, sync2, comp2, single等)
            for(int j = 1; j < _stat.cnt[i]; j++){
                if(self->stat & (1 << _stat.ch[i][j].bit)){
                    mp_printf(print, ", %s", _stat.ch[i][j].info);
                }
            }
            // 翻转位, 周期, 捕获缓存大小
            if((i == 0 && self->stat & 1<<10) ||  (i == 4 && self->stat & 1<<11)){  // PWM0, PWM4为输入捕获
                mp_printf(print, ", capinv=%s, rxbuf=%d", (_pwm_get_inv(&self->hpwm[i], true) ? ("true") : ("false")), ARR_LEN(self->cap[i>>2].capbuf));
            }else{
                mp_printf(print, ", pwminv=%s, period=%d", (_pwm_get_inv(&self->hpwm[i], false) ? ("true") : ("false")), period);
            }
            // 添加")\n"
            mp_printf(print, ")\n");
        }
    }
    mp_printf(print, "], stat=0x%x)\n", self->stat);
}

// check pwm state before init
void _pwm_state_check(machine_pwm_obj_t* self, int ch, uint32_t outmode, bool is_cap){
    
    // verify pwm channel between 0 to 4
    if(ch < 0 || ch >= MICROPY_HW_MAX_PWM_CNT){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) doesn't exist"), ch);
    }
    // just pwm0 and pwm4 allowed to cfg cap mode
    if(is_cap && ch != 0 && ch != 4){     
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) config to capture failed"), ch);
    }
     
    // self->stat desc:
    // title          independent              sync2    sync5    comp2     cap0   cap1
    //        |------------------------|    |--------|    |   |--------|    |      | 
    //       ---------------------------------------------------------------------------
    // bits  |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  10  |  11  |
    //       --------------------------------------------------------------------------- 
    // desc   PWM0  PWM1  PWM2   PWM3  PWM4 PWM01|PWM23  ALL  PWM01|PWM23  PWM0   PWM4 
    //---------------------------------------------------------------------------------------
    switch(outmode){
    case PWM_OUT_MODE_5SYNC: 
        if(ch != 0){
            mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg sync5 failed, just allowed PWM0"), ch);
        }
        if(self->stat & 0x1E){ 
            mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg sync5 failed, stat(0x%x) conflict"), ch, self->stat);
        }
        break;
        
    case PWM_OUT_MODE_2COMPLEMENTARY:
    case PWM_OUT_MODE_2SYNC:
        // ch=0, outmode=COMP2 or SYNC2 means reconfigure, otherwise, channels were ocuppied
        if(ch != 0 && self->stat & (1<<7)){ // sync5
             mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg sync2 or comp2 failed, sync5 bit set."), ch);
        }
        // eigher channel 0 or channel 2 can configure to sync2 or comp2
        if(ch >= 4 || ch & 0x01){
            mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg sync2 or comp2 failed, just allowed pwm[0, 2]"), ch);
        }
        // check wheter next channel was occupied.
        if(self->stat & (1 << (ch + 1))){
            mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg sync2 or comp2 failed,  stat(0x%x) conflict"), ch, self->stat);
        }
        break;
        
    default:  // PWM_OUT_MODE_INDEPENDENT
        // check sync5
        if(ch != 0 && self->stat & (1<<7)){ // sync5
             mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg single failed, sync5 bit set."), ch);
        }
        // check sync2, comp2(pwm0, pwm2 is master, check salver pwm1 and pwm3)
        if(ch % 2){
            if(self->stat & (1 << (5+ch/2) | 1<<(8+ch/2))){  // bit[5:6]-sync2, bit[8:9]-comp2
                mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cfg single failed,stat(0x%x) conflict"), ch, self->stat);
            } 
        }
        break;
    }
}

// make a patch, after HAL_PWM_Init(), to support capture mode
void _pwm_patch_init(machine_pwm_obj_t* pwm_obj, int ch, bool iscap, bool capinv)
{
    // set pwm mode or cap mode
    if (pwm_obj->hpwm[ch].Channel == 0){
        WRITE_BIT(pwm_obj->hpwm[ch].Instance->CR, PWM_CR_CPEN_Pos, iscap);              // set pwm mode or cap mode
        WRITE_BIT(pwm_obj->hpwm[ch].Instance->CR, PWM_CR_POEN_Pos, iscap);              // set gpio mode to output or tristate(input)
        WRITE_BIT(pwm_obj->hpwm[ch].Instance->CR, PWM_CR_CAPINV_Pos, capinv);           // inverse input signal(cap mode)
    }else if(pwm_obj->hpwm[ch].Channel == 4){
        WRITE_BIT(pwm_obj->hpwm[ch].Instance->CH4CR3, PWM_CH4CR3_CPEN_Pos, iscap);      // set pwm mode or cap mode
        WRITE_BIT(pwm_obj->hpwm[ch].Instance->CH4CR3, PWM_CH4CR3_POEN_Pos, iscap);      // set gpio mode to output or tristate(input)
        WRITE_BIT(pwm_obj->hpwm[ch].Instance->CH4CR3, PWM_CH4CR3_CAPINV_Pos, capinv);  // inverse input signal(cap mode)
    }
    //_pwm_cap_mode_sel(pwm_obj->hpwm + ch, iscap);  // set pwm mode or cap mode
    //_pwm_cap_gpio_cfg(pwm_obj->hpwm + ch, iscap);  // set gpio mode to output or tristate(input)
    //_pwm_cap_set_inv(pwm_obj->hpwm + ch, capinv);  // wheter inverse input signal(if channel used by cap mode)
    if(iscap){
        // disable FLIEN, RLIEN(rising level interrupt enable)
        CLEAR_BIT(pwm_obj->hpwm[ch].Instance->IE, PWM_IE_FLIEN|PWM_IE_RLIEN);
        CLEAR_BIT(pwm_obj->hpwm[ch].Instance->CH4CR3, PWM_CH4CR3_FLIEN|PWM_CH4CR3_RLIEN);
        if(pwm_obj->hpwm[ch].Channel == 0){
            SET_BIT(pwm_obj->hpwm[ch].Instance->IE, PWM_IE_DMAEN);          // enable DMA transfer, cap0(pwm0)
        }else{
            SET_BIT(pwm_obj->hpwm[ch].Instance->CH4CR3, PWM_CH4CR3_DMAEN);  // enable DMA transfer, cap1(pwm4)
        } 
        pwm_obj->stat |= 1 << (10 + (pwm_obj->hpwm[ch].Channel>>2));        // set stat bit10(cap0) or bit11(cap)
    }

}


// all args:
// ch=int, outmode=["sync2", "sybc5", "comp2", "single"], cntmode=["edgeup", "edgedwn", "centeral"], reload=bool, psc=int, period=int, pwminv=bool, cap=bool, capinv=bool
// PWM args: init(ch, outmode, cntmode, reload, psc, period, pwminv)
// CAP args: init(ch, psc, cap, capinv), by default, cntmode="edgeup", reload=True
// freq = 40M/(psc+1)/(period+1)
STATIC mp_obj_t machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
    
    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_ch, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} },
        { MP_QSTR_outmode, MP_ARG_INT, {.u_int = PWM_OUT_MODE_INDEPENDENT} },
        { MP_QSTR_cntmode, MP_ARG_INT, {.u_int = PWM_COUNTERMODE_EDGEALIGNED_DOWN} },
        { MP_QSTR_reload, MP_ARG_BOOL, {.u_bool = true} },
        { MP_QSTR_psc, MP_ARG_INT, {.u_int = 4-1} },
        { MP_QSTR_period, MP_ARG_INT, {.u_int = 250-1} },
        { MP_QSTR_pwminv, MP_ARG_BOOL, {.u_bool = false} },
        { MP_QSTR_cap, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
        { MP_QSTR_capinv, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
    };
    // parse args
    //6enum { ARG_ch, ARG_outmode, ARG_cntmode, ARG_reload, ARG_psc, ARG_period, ARG_pwminv, ARG_cap, ARG_capinv};
    struct {
        mp_arg_val_t ch, outmode, cntmode, reload, psc, period, pwminv, cap, capinv;
    } args;
    mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args);
    
    //printf("outmode:%d, cntmode:%d, reload:%d, psc:%d\n", args.outmode.u_int, args.cntmode.u_int, args.reload.u_bool, args.psc.u_int);
    
    // check for specific configure(eg, ch2, outmode=sync5, wasn't allowed)
    _pwm_state_check(self, args.ch.u_int, args.outmode.u_int, args.cap.u_bool);
    
    // reset specific channel
    _pwm_deinit(self->hpwm + args.ch.u_int);

    // init pwm channel
    self->hpwm[args.ch.u_int].Instance = PWM;
    self->hpwm[args.ch.u_int].Init.CounterMode = (args.cap.u_bool) ? (PWM_COUNTERMODE_EDGEALIGNED_UP) : (args.cntmode.u_int);
    self->hpwm[args.ch.u_int].Init.Prescaler = args.psc.u_int;
    self->hpwm[args.ch.u_int].Init.Period = args.period.u_int;
    self->hpwm[args.ch.u_int].Init.Pulse = args.period.u_int / 2;
    self->hpwm[args.ch.u_int].Init.OutMode = args.outmode.u_int;
    self->hpwm[args.ch.u_int].Init.AutoReloadPreload = (args.reload.u_bool || args.cap.u_bool) ? (PWM_AUTORELOAD_PRELOAD_ENABLE) : (PWM_AUTORELOAD_PRELOAD_DISABLE);
    self->hpwm[args.ch.u_int].Init.OutInverse = (args.pwminv.u_bool) ? (PWM_OUT_INVERSE_ENABLE) : (PWM_OUT_INVERSE_DISABLE);
    self->hpwm[args.ch.u_int].Channel = args.ch.u_int;
    
    HAL_PWM_Init(self->hpwm + args.ch.u_int);
    _pwm_patch_init(self, args.ch.u_int, args.cap.u_bool, args.capinv.u_bool);  // patch init, to support capture mode
    
    return mp_const_none;
}


STATIC mp_obj_t machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {

    machine_pwm_obj_t* self;
    // pwm id
    //int pwm_id = mp_obj_get_int(args[0]);
    //if (pwm_id < 0 || pwm_id > MICROPY_HW_MAX_pwm_CNT) {
    //    mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("pwm(%d) doesn't exist"), pwm_id);
    //}
    // alloc memory
    if(MP_STATE_PORT(pwm_obj) == NULL){
        self = m_new0(machine_pwm_obj_t, 1);
        self->base.type = &machine_pwm_type;
        MP_STATE_PORT(pwm_obj) = self;
    }else{
        self = MP_STATE_PORT(pwm_obj);
    }
    
    // return sington, use obj.init(), obj.deinit() to init/deinit the specific channel
    //if (n_args > 1 || n_kw > 0) {
    //    // start the peripheral
    //    mp_map_t kw_args;
    //    mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
    //    machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args);
    //}
    return MP_OBJ_FROM_PTR(self);

}

// PWM args: init(ch, outmode, cntmode, reload, psc, period, pwminv)
// CAP args: init(ch, psc, cap, capinv), by default, cntmode="edgeup", reload=True
STATIC mp_obj_t machine_pwm_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(args[0]);
    return machine_pwm_init_helper(self, n_args - 1, args + 1, kw_args);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pwm_init_obj, 1, machine_pwm_init);

// deinit specific channel, if ch<0, reset pwm module
STATIC mp_obj_t machine_pwm_deinit(mp_obj_t self_in, mp_obj_t ch_o) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_int_t ch = mp_obj_get_int(ch_o);
    if(ch > 4){
        mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("PWM deinit failed, channel invalid"));
    }
    if(ch < 0){
        ch = 0;
        //self->hpwm[ch].Channel = 0;
        self->reset_module = true;
    }else{
        self->reset_module = false;
    }
    _pwm_deinit(self->hpwm + ch);
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_pwm_deinit_obj, machine_pwm_deinit);

// start pwm counter
STATIC mp_obj_t machine_pwm_start(mp_obj_t self_in, mp_obj_t ch_o) {
    mp_int_t ch = mp_obj_get_int(ch_o);
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    
    if(ch < 0 || ch >= MICROPY_HW_MAX_PWM_CNT){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) start failed, channel invalid"), ch);
    }
    HAL_PWM_Start(&self->hpwm[ch], ch);
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_pwm_start_obj, machine_pwm_start);

// stop pwm counter
STATIC mp_obj_t machine_pwm_stop(mp_obj_t self_in, mp_obj_t ch_o) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_int_t ch = mp_obj_get_int(ch_o);
    
    if(ch < 0 || ch >= MICROPY_HW_MAX_PWM_CNT){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) stop failed, channel invalid"), ch);
    }
    HAL_PWM_Stop(&self->hpwm[ch], ch);
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_pwm_stop_obj, machine_pwm_stop);

// set pwm duty
// pwm.set_duty(ch, duty), ch=[0, 4], duty=[0, period-1]
STATIC mp_obj_t machine_pwm_set_duty(mp_obj_t self_in, mp_obj_t ch_o, mp_obj_t duty_o) {
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    mp_int_t ch = mp_obj_get_int(ch_o);
    mp_int_t duty = abs(mp_obj_get_int(duty_o));
    
    if(ch < 0 || ch >= MICROPY_HW_MAX_PWM_CNT){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) set duty failed, channel invalid"), ch);
    }
    
    // verify duty in range [0, period]
    duty = (duty < self->hpwm[ch].Init.Period) ? (duty) : (self->hpwm[ch].Init.Period);
    HAL_PWM_Duty_Set(&self->hpwm[ch], ch, duty);
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_pwm_set_duty_obj, machine_pwm_set_duty);


// capture start
STATIC mp_obj_t machine_cap_start(mp_obj_t self_in, mp_obj_t ch_o) {
    mp_int_t ch = mp_obj_get_int(ch_o);
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    
    if((ch != 0 && ch != 4) || !(self->stat & (1<<(10+ch/4)))){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cap start failed, channel error or uninit to cap mode"), ch);
    }
    _pwm_cap_start(self->hpwm + ch, self->cap + ch / 4);
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_cap_start_obj, machine_cap_start);

// capture stop
STATIC mp_obj_t machine_cap_stop(mp_obj_t self_in, mp_obj_t ch_o) {
    mp_int_t ch = mp_obj_get_int(ch_o);
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    
    if((ch != 0 && ch != 4) || !(self->stat & 1<<(10+ch/4))){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) cap stop failed, channel error or uninit to cap mode"), ch);
    }
    _pwm_cap_stop(self->hpwm + ch, self->cap + ch / 4);
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_cap_stop_obj, machine_cap_stop);

// get capture data
// return tuple, tuple[2*i]-FCAPDAT, tuple[2*i+1]-RCAPDAT, R-CAP-DAT, R for rising edge, F for falling
STATIC mp_obj_t machine_cap_get_data(mp_obj_t self_in, mp_obj_t ch_o) {
    mp_int_t ch = mp_obj_get_int(ch_o);
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    
    if((ch != 0 && ch != 4)){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) get cap data failed, channel invalid"), ch);
    }
    // each uint32_t split to FCAPDAT(high 16 bits), RCAPDAT(low 16 bits)
    mp_obj_tuple_t* tuple_o = mp_obj_new_tuple(ARR_LEN(self->cap[ch>>2].capbuf) * 2, NULL);
    for(int i = 0; i < ARR_LEN(self->cap[ch>>2].capbuf); i++){
        tuple_o->items[2*i+0] = MP_OBJ_NEW_SMALL_INT(self->cap[ch>>2].capbuf[i] >> 16);     // FCAPDAT, falling level
        tuple_o->items[2*i+1] = MP_OBJ_NEW_SMALL_INT(self->cap[ch>>2].capbuf[i] & 0xFFFF);  // RCAPDAT, rising level
    }
    return MP_OBJ_TO_PTR(tuple_o);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_cap_get_data_obj, machine_cap_get_data);


// pwm mode return freq_pwm=APBCLK(40MHz) / (psc+1) / (period+1)
// cap mode return freq_cap=APBCLK(40MHz) / (psc+1)
STATIC mp_obj_t machine_get_freq(mp_obj_t self_in, mp_obj_t ch_o) {
    wm_sys_clk sysclk;
    mp_int_t ch = mp_obj_get_int(ch_o);
    machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in);
    
    // verify pwm channel in range 0 ~ 4
    if(ch < 0 || ch >= MICROPY_HW_MAX_PWM_CNT){
        mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM(%d) get freq failed, channel invalid"), ch);
    }
    // get sysclock
    SystemClock_Get(&sysclk); // normally apbclk=40Mhz, but read it from register
    // get apb freq
    if((ch == 0 || ch == 4) && (self->stat & (1<<(10+ch/4)))){  // capmode
        // 这个40M要从sys里读取, 具体的时钟
        return MP_OBJ_NEW_SMALL_INT((sysclk.apbclk * UNIT_MHZ) / (self->hpwm[ch].Init.Prescaler + 1));
        
    }else{  // pwm mode
        return MP_OBJ_NEW_SMALL_INT((sysclk.apbclk * UNIT_MHZ) / (self->hpwm[ch].Init.Prescaler + 1) / (self->hpwm[ch].Init.Period + 1));
    }
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_get_freq_obj, machine_get_freq);


STATIC const mp_rom_map_elem_t machine_pwm_locals_dict_table[] = {
    // pwm init/deinit
    { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pwm_init_obj) },
    { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_pwm_deinit_obj) },
    // start, stop pwm, set duty
    { MP_ROM_QSTR(MP_QSTR_pwm_start), MP_ROM_PTR(&machine_pwm_start_obj) },
    { MP_ROM_QSTR(MP_QSTR_pwm_stop), MP_ROM_PTR(&machine_pwm_stop_obj) },
    { MP_ROM_QSTR(MP_QSTR_pwm_duty), MP_ROM_PTR(&machine_pwm_set_duty_obj) },
    // start, stop cap, get cap data
    { MP_ROM_QSTR(MP_QSTR_cap_start), MP_ROM_PTR(&machine_cap_start_obj) },
    { MP_ROM_QSTR(MP_QSTR_cap_stop), MP_ROM_PTR(&machine_cap_stop_obj) },
    { MP_ROM_QSTR(MP_QSTR_cap_data), MP_ROM_PTR(&machine_cap_get_data_obj) },
    // get freq
    { MP_ROM_QSTR(MP_QSTR_get_freq), MP_ROM_PTR(&machine_get_freq_obj) },
    // countmode, if cap=True, countmode must be edgeup
    { MP_ROM_QSTR(MP_QSTR_edgeup), MP_ROM_INT(PWM_COUNTERMODE_EDGEALIGNED_UP) },
    { MP_ROM_QSTR(MP_QSTR_edgedwn), MP_ROM_INT(PWM_COUNTERMODE_EDGEALIGNED_DOWN) },
    { MP_ROM_QSTR(MP_QSTR_centeral), MP_ROM_INT(PWM_COUNTERMODE_CENTERALIGNED) },
    // outmode
    { MP_ROM_QSTR(MP_QSTR_sync2), MP_ROM_INT(PWM_OUT_MODE_2SYNC)},
    { MP_ROM_QSTR(MP_QSTR_sync5), MP_ROM_INT(PWM_OUT_MODE_5SYNC) },
    { MP_ROM_QSTR(MP_QSTR_comp2), MP_ROM_INT(PWM_OUT_MODE_2COMPLEMENTARY) },
    { MP_ROM_QSTR(MP_QSTR_single), MP_ROM_INT(PWM_OUT_MODE_INDEPENDENT) },
    
};
STATIC MP_DEFINE_CONST_DICT(machine_pwm_locals_dict, machine_pwm_locals_dict_table);

const mp_obj_type_t machine_pwm_type = {
    { &mp_type_type },
    .name = MP_QSTR_PWM,
    .print = machine_pwm_print,
    .make_new = machine_pwm_make_new,
    .locals_dict = (mp_obj_dict_t *)&machine_pwm_locals_dict,
};
